package com.zhuhjay.service.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhuhjay.common.base.constant.ApplicationConstant;
import com.zhuhjay.common.base.exception.BasicBusinessException;
import com.zhuhjay.common.base.util.BeanBuilder;
import com.zhuhjay.common.base.util.SpringContextHolder;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.service.system.mapper.SystemRoleMapper;
import com.zhuhjay.service.system.model.dto.RoleDto;
import com.zhuhjay.service.system.model.entity.SystemMenu;
import com.zhuhjay.service.system.model.entity.SystemRole;
import com.zhuhjay.service.system.model.entity.SystemRoleMenu;
import com.zhuhjay.service.system.model.entity.SystemUserRole;
import com.zhuhjay.service.system.model.vo.RoleInfoVo;
import com.zhuhjay.service.system.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.zhuhjay.common.base.constant.CacheConstant.ROLE_MENUS_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Zhuhjay
 * @since 2023-02-13
 */
@Slf4j
@Service
public class SystemRoleServiceImpl extends ServiceImpl<SystemRoleMapper, SystemRole> implements SystemRoleService {

    @Resource
    private SystemUserRoleService userRoleService;
    @Resource
    private SystemRoleMenuService roleMenuService;
    @Resource
    private SystemMenuService menuService;
    @Resource
    private SystemConfigService configService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bindToUser(Integer id) {
        // 绑定用户, 通过系统配置获取默认的用户角色
        String roleIdStr = configService.getConfigValue("system.register.user.role");
        int roleId;
        // 如果配置项为空, 或者配置项中的角色id不存在, 或者配置项中的角色id为超级管理员角色id, 则抛出异常
        if (StrUtil.isBlank(roleIdStr) ||
                this.getById(roleId = Integer.parseInt(roleIdStr)) == null ||
                roleId == ApplicationConstant.SUPER_ADMIN_ROLE_ID) {
            log.error("系统配置错误, 请检查${{}}配置项", "system.register.user.role");
            throw new BasicBusinessException("注册失败, 请联系管理员");
        }
        SystemUserRole userRole = new SystemUserRole().setUserId(id).setRoleId(roleId);
        return userRoleService.save(userRole);
    }

    @Override
    public SystemRole getRoleByUserId(Integer id) {
        // 根据用户id获取用户的角色信息
        SystemUserRole userRole = userRoleService.lambdaQuery()
                .eq(SystemUserRole::getUserId, id).one();
        return this.lambdaQuery().eq(SystemRole::getId, userRole.getRoleId()).one();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(RoleDto dto) {
        // 查看数据库中是否存在该key
        if (this.lambdaQuery().eq(SystemRole::getRoleKey, dto.getRoleKey()).one() != null) {
            throw new BasicBusinessException("角色key已存在");
        }
        SystemRole role = new SystemRole();
        role.setRoleKey(dto.getRoleKey()).setRoleName(dto.getRoleName()).setSort(dto.getSort());
        // 需要将数据库角色排序进行重排, 默认数据库中的排序已经有序
        List<SystemRole> roles = this.lambdaQuery().orderByAsc(SystemRole::getSort).list();
        // 如果新增的sort大于数据库中的最大sort, 则直接添加到最后
        if (role.getSort() > roles.size()) {
            role.setSort(roles.size() + 1);
        } else {
            roles.stream().filter(r -> r.getSort() >= role.getSort())
                    .forEach(r -> r.setSort(r.getSort() + 1));
            this.updateBatchById(roles);
        }
        this.save(role);
        // 更新角色与菜单的关联关系
        SpringContextHolder.getBean(SystemRoleService.class).bindToMenu(role.getId(), dto.getMenuIds());
    }

    /**
     * 验证所给的菜单id是否都存在, 并将其中的叶子菜单id收集起来并返回
     **/
    private List<Integer> checkExistAndGetLeafMenuIds(List<Integer> menuIds) {
        List<Integer> leafMenuIds = new ArrayList<>();
        // 是否存在菜单id
        if (menuService.lambdaQuery().in(SystemMenu::getId, menuIds).count() != menuIds.size()) {
            throw new BasicBusinessException("有不存在菜单id");
        }
        menuIds.forEach(menuId -> {
            // 如果该菜单id不出现在parentId, 证明其为叶子节点
            if (menuService.lambdaQuery().eq(SystemMenu::getParentId, menuId).count() == 0) {
                leafMenuIds.add(menuId);
            }
        });
        return leafMenuIds;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Integer id, RoleDto dto) {
        SystemRole role = this.getById(id);
        if (role == null) {
            throw new BasicBusinessException("角色不存在");
        }
        // 验证并获取叶子菜单id
        List<Integer> leafMenuIds = checkExistAndGetLeafMenuIds(dto.getMenuIds());

        boolean isUpdate = false;
        // 如果角色key发生了变化, 则需要判断是否存在该key
        if (!role.getRoleKey().equals(dto.getRoleKey())) {
            this.lambdaQuery().eq(SystemRole::getRoleKey, dto.getRoleKey())
                    .oneOpt().ifPresent(r -> {
                        throw new BasicBusinessException("角色key已存在");
                    });
            role.setRoleKey(dto.getRoleKey());
            isUpdate = true;
        }
        // 如果角色名称发生了变化, 则需要进行更新
        if (!role.getRoleName().equals(dto.getRoleName())) {
            role.setRoleName(dto.getRoleName());
            isUpdate = true;
        }
        // 如果排序发生了变化, 则需要进行排序
        List<SystemRole> roles = this.lambdaQuery().orderByAsc(SystemRole::getSort).list();
        if (!role.getSort().equals(dto.getSort())) {
            // 对排序字段进行重排
            reorderSort(dto, role, roles);
            this.updateBatchById(roles);
            isUpdate = true;
        }
        if (isUpdate) {
            this.updateById(role);
        }
        // 更新角色与菜单的关联关系
        SpringContextHolder.getBean(SystemRoleService.class).bindToMenu(id, leafMenuIds);
    }

    private static void reorderSort(RoleDto dto, SystemRole role, List<SystemRole> roles) {
        // 删除原来的角色
        roles.remove(role.getSort() - 1);
        // 如果排序大于数据库中的最大sort, 则直接添加到最后
        if (dto.getSort() > roles.size()) {
            // 设置新的角色
            roles.add(role);
        } else {
            roles.add(dto.getSort() - 1, role);
        }
        // 重新排序
        for (int i = 0; i < roles.size(); i++) {
            roles.get(i).setSort(i + 1);
        }
        // 删除掉本次修改的角色, 只进行其他角色的更新
        roles.remove(role);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id) {
        // 查看数据库中是否存在该数据
        SystemRole role = this.getById(id);
        if (role == null) {
            throw new BasicBusinessException("该角色不存在");
        }
        // 如果该角色被使用, 则不能删除
        if (userRoleService.lambdaQuery().eq(SystemUserRole::getRoleId, id).count().intValue() > 0) {
            throw new BasicBusinessException("该角色已被关联使用, 不能删除");
        }
        // 删除角色, 同时需要将该角色与菜单的关联关系删除
        this.removeById(id);
        roleMenuService.lambdaUpdate().eq(SystemRoleMenu::getRoleId, id).remove();
        // 将排序大于当前角色的角色排序-1
        this.lambdaUpdate()
                .gt(SystemRole::getSort, role.getSort())
                .setSql("sort = sort - 1").update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindToMenu(Integer id, List<Integer> leafMenuIds) {
        // 判断是否需要进行更新菜单与角色的关联关系
        if (needUpdateMenu(id, leafMenuIds)) {
            // 先删除原本的关联关系
            roleMenuService.lambdaUpdate().eq(SystemRoleMenu::getRoleId, id).remove();
            // 添加新的关联关系
            List<SystemRoleMenu> roleMenus = new ArrayList<>(leafMenuIds.size());
            leafMenuIds.forEach(menuId -> roleMenus.add(new SystemRoleMenu().setRoleId(id).setMenuId(menuId)));
            roleMenuService.saveBatch(roleMenus);
            // 删除角色菜单缓存
            RedisUtils.deleteCache(ROLE_MENUS_PREFIX + id);
        }
    }

    /**
     * 判断是否需要更新菜单与角色的关联关系
     *
     * @param id          角色id
     * @param leafMenuIds 叶子节点菜单id
     * @return 是否需要更新
     */
    private boolean needUpdateMenu(Integer id, List<Integer> leafMenuIds) {
        // 绑定了多少个叶子菜单
        Long leafBindCount = roleMenuService.lambdaQuery()
                .eq(SystemRoleMenu::getRoleId, id).in(SystemRoleMenu::getMenuId, leafMenuIds)
                .count();
        // 数据库中存在的叶子菜单数
        Long dbBindCount = roleMenuService.lambdaQuery().eq(SystemRoleMenu::getRoleId, id).count();
        // 情况：
        //  1. 现需绑定的叶子数量 != 数据库中绑定的叶子数量 -> 需要更新
        //  2. 如果出现 = 的情况, 需要绑定的叶子菜单存在于数据库中的数量 != 数据库中绑定的叶子数量 -> 需要更新
        return leafMenuIds.size() != dbBindCount || !leafBindCount.equals(dbBindCount);
    }

    @Override
    public List<RoleInfoVo> getList(Boolean needMenus) {
        return this.lambdaQuery().orderByAsc(SystemRole::getSort).list()
                .stream().map(role -> {
                    // 获取角色关联的菜单id(如果需要)
                    List<Integer> collect = null;
                    // 获取角色关联的菜单id
                    if (needMenus) {
                        // 如果是超级管理员角色, 则直接返回所有菜单
                        if (ApplicationConstant.SUPER_ADMIN_ROLE_ID.equals(role.getId())) {
                            collect = menuService.lambdaQuery().orderByAsc(SystemMenu::getSort)
                                    .list().stream().map(SystemMenu::getId).collect(Collectors.toList());
                        } else {
                            collect = roleMenuService.lambdaQuery()
                                    .eq(SystemRoleMenu::getRoleId, role.getId()).orderByAsc(SystemRoleMenu::getMenuId)
                                    .list().stream().map(SystemRoleMenu::getMenuId).collect(Collectors.toList());
                        }
                    }
                    return BeanBuilder.builder(RoleInfoVo::new)
                            .with(RoleInfoVo::setId, role.getId())
                            .with(RoleInfoVo::setRoleKey, role.getRoleKey())
                            .with(RoleInfoVo::setRoleName, role.getRoleName())
                            .with(RoleInfoVo::setSort, role.getSort())
                            .with(RoleInfoVo::setMenuIds, collect).build();
                }).collect(Collectors.toList());
    }
}
